【React 19】サクッと理解するuseActionState

【React 19】サクッと理解するuseActionState

React 19で導入予定のuseActionStateフックについて詳しく解説します。フォームアクションの結果に基づくステート更新や非同期アクションの状態管理を簡素化するこの新機能の使い方や利点を具体的な実装例をもとに紹介します。
Clock Icon2024.10.11

こんにちは、戸田です。
(2024/10/11 現在) React19 がメジャーバージョンになる前に新機能の useActionState について理解を深めようと思ったのでまとめました。

公式ドキュメント

https://ja.react.dev/reference/react/useActionState

useActionState の詳細と使い方

React 19 で導入されるuseActionStateは、フォームアクションの結果に基づいてステートを更新するためのフックです。このフックを使用することで、非同期アクションの状態管理が簡素化され、特にフォーム処理においてとても便利です。

useActionState とは

useActionStateは以下の特徴を持つフックです:

  1. フォームアクションの結果に基づいてステートを更新する
  2. 非同期アクションの実行状態(isPending)、成功時・エラー時の状態(state)を一元管理する
  3. Server Components 環境下('use server'を使用している時)では、JavaScript が実行される前にフォームを操作可能にする

基本的な使い方

const [state, formAction, isPending] = useActionState(action, initialState, permalink?);
  • action: フォーム送信時に呼び出される関数
  • initialState: ステートの初期値
  • permalink: (オプション)ユニークなページ URL を含む文字列
    permalink は最初の段階で理解する必要はないかと思います。

返り値:

  1. state: 現在のステート
  2. formAction: フォームのactionプロパティに渡すアクション
  3. isPending: アクションが実行中かどうかを示すブール値(useTransition と同じ)

具体的な実装例

/src/components/LoginForm.tsx
import { useActionState } from "react";

// ログインフォームの状態を定義する型
type LoginFormStateType = {
  success: boolean;
};

// ログイン処理をシミュレートする非同期関数
const fetchLogin = async (name, password): Promise<LoginFormStateType> => {
  // ここで認証をする(今回は通信時間を3秒として模倣)
  await new Promise((resolve) => setTimeout(resolve, 3000));
  // 実際はここで認証結果を返す
  return { success: true };
};

// ログインフォームのアクション(フォーム送信時に実行される)
const LoginFormAction = async (prevState: LoginFormStateType, formData: FormData): Promise<LoginFormStateType> => {
  //名前とパスワードを取得
  const name = formData.get("name");
  const password = formData.get("password");
  // ログイン処理を実行し、結果を取得
  const response = await fetchLogin(name, password);
  // ログイン結果を返す
  return response;
};

export const LoginForm = () => {
  // useActionStateフックを使用してフォームの状態、アクション、ローディング状態を管理
  const [formState, formAction, isLoading] = useActionState(LoginFormAction, { success: false });
  // 現在のフォーム状態をコンソールに出力(デバッグ用)
  console.log(formState);

  // ログイン結果メッセージを返す関数
  const resultMessage = () => {
    return formState.success ? "成功" : "エラー";
  };

  return (
    <>
      {/* フォーム要素。actionにformActionを指定してフォーム送信時の挙動を制御 */}
      <form action={formAction}>
        <div>
          {/* ユーザー名入力フィールド */}
          <label>Username: </label>
          <input type="text" name="name" />
        </div>
        <div>
          {/* パスワード入力フィールド */}
          <label>Password: </label>
          <input type="password" name="password" />
        </div>
        <button type="submit">submit</button> {/* 送信ボタン */}
      </form>
      {/* ローディング中は"loading..."を表示し、それ以外はログイン結果メッセージを表示 */}
      {isLoading ? <div>loading...</div> : resultMessage()}
    </>
  );
};
コメントなし ver
import { useActionState } from "react";

type LoginFormStateType = {
  success: boolean;
};

const fetchLogin = async (name, password): Promise<LoginFormStateType> => {
  await new Promise((resolve) => setTimeout(resolve, 3000));
  return { success: true };
};

const LoginFormAction = async (prevState: LoginFormStateType, formData: FormData): Promise<LoginFormStateType> => {
  const name = formData.get("name");
  const password = formData.get("password");
  const response = await fetchLogin(name, password);
  return response;
};

export const LoginForm = () => {
  const [formState, formAction, isLoading] = useActionState(LoginFormAction, { success: false });
  console.log(formState);

  const resultMessage = () => {
    return formState.success ? "成功" : "エラー";
  };

  return (
    <>
      <form action={formAction}>
        <div>
          <label>Username: </label>
          <input type="text" name="name" />
        </div>
        <div>
          <label>Password: </label>
          <input type="password" name="password" />
        </div>
        <button type="submit">submit</button>
      </form>
      {isLoading ? <div>loading...</div> : resultMessage()}
    </>
  );
};

この例では、username, password を使用したログインフォームの処理を useActionState で管理しています。

手順

  1. useActionState には LoginFormAction(formAction で実行する関数)と formState の初期値を渡す
  2. form に入力して submit すると formAction が実行される
  3. LoginFormAction では前回の state と formData を受け取り処理をする
  4. formState に LoginFormAction の戻り値が代入される
  5. 2 ~ 4 の間に isLoading が true になるのでローディング表示をする

エラーハンドリング

今回はエラーハンドリングをしていませんが、LoginFormAction 内で catch したエラーを errorMessage に入力することで実装できます。

type LoginFormStateType = {
  success: boolean;
  errorMessage?: string; //LoginFormAction内で定義
};

useActionState の利点

  1. 状態管理の簡素化: 非同期アクションの状態管理が自動化され、コードがクリーンになります。

  2. エラーハンドリングの改善: アクションのエラー状態を簡単に管理できます。

  3. Server Actions との相性: JavaScript が読み込まれる前でもフォームが機能するため、ユーザー体験が向上します。

  4. プログレッシブエンハンスメント: permalinkオプションを使用することで、JavaScript が無効な環境でもフォームが機能します。

注意点

  • useActionStateに渡す関数は、最初の引数として前回のステート(または初期ステート)を受け取ります。
  • Server Actions を使用している場合、クライアントサイドの JavaScript が実行される前にフォームが操作可能になります。

まとめ

useActionStateは、React 19 で導入された非常に便利なフックです。フォーム処理や非同期アクションの状態管理を簡素化し、より効率的で読みやすいコードを書くことができます。特に Server Actions と組み合わせることで、パフォーマンスとユーザー体験の向上が期待できます。

参考資料

https://ja.react.dev/reference/react/useActionState

https://zenn.dev/uhyo/books/react-19-new/viewer/useactionstate

https://zenn.dev/uhyo/books/react-19-new/viewer/form-action

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.